import type {
    ColDef,
    GridOptions,
    NewValueParams,
    IRowNode,
} from '@ag-grid-community/core';

import { assert } from '@/lib/utils.ts';
import { postJson } from '@/lib/request';
import { createAndInitilalizeTable } from '@/components/table/lib';

interface TableManagerConfig {
    submitChangedDataApiUrl: string;
}

class TableManager {
    private changedRowDataSet: Set<IRowNode>;
    private config: TableManagerConfig;
    
    constructor(config: TableManagerConfig) {
        this.changedRowDataSet = new Set();
        this.config = config;
        this.onCellValueChanged = this.onCellValueChanged.bind(this);
        this.saveChangedBtnClickEvent = this.saveChangedBtnClickEvent.bind(this);
    }

    onCellValueChanged(p: NewValueParams): void {
        if (!p.node) return;
        this.changedRowDataSet.add(p.node);
        // We need to redraw this row since `getRowClass` execute earler than
        // `onCellValueChanged`
        p.api.redrawRows({ rowNodes: [p.node] });
    }

    addOnCellValueChangedToColDef(colDef: ColDef): ColDef {
        return {
            ...colDef,
            onCellValueChanged: this.onCellValueChanged
        };
    }
    

    async saveChangedBtnClickEvent(): Promise<void> {
        try {
            if (this.changedRowDataSet.size === 0) {
                alert(gettext("没有需要保存的更改"));
                return;
            }

            const data = Array.from(this.changedRowDataSet, node => node.data);

            await postJson(this.config.submitChangedDataApiUrl, data);

            alert(gettext("保存成功"));
            this.changedRowDataSet.clear();
            window.location.reload();
        } catch (error) {
            alert(error instanceof Error ? error.message : gettext("保存失败"));
        }
    }

    createAndInitializeTable(
        table_elem: HTMLElement,
        url: string,
        options: Partial<GridOptions> = {}
    ) {
        return createAndInitilalizeTable(
            url,
            table_elem,
            {
                getRowClass: (params) => {
                    let css_classes: string[] = ["tw:border-l-transparent"];
                    if (params.api.getDisplayedLeftColumns().length) {
                        css_classes.push("tw:[.ag-body-viewport>div:first-child_&]:border-l-2");
                    } else {
                        css_classes.push("tw:[.ag-body-viewport>div:not([class~=ag-pinned-right-cols-container])>div_&]:border-l-2");
                    }

                    if (this.changedRowDataSet.has(params.node)) {
                        css_classes.push("tw:!border-l-orange-300");
                    }
                    return css_classes;
                },

                ...options
            }
        );
    }
}

interface RequiredSetupEditablePageConfig {
    getDataApiUrl: string;
    submitChangedDataApiUrl: string;
    getOptions: (manager: TableManager) => Partial<GridOptions>
}

interface OptionalSetupEditablePageConfig {
    table_elem?: HTMLElement;
    save_changes_btn_elem?: HTMLElement;
}

type SetupEditablePageConfig = RequiredSetupEditablePageConfig & Partial<OptionalSetupEditablePageConfig>;

export function setupEditablePage(config: SetupEditablePageConfig): TableManager {
    if (!config.table_elem) {
        const table_elem = document.getElementById("table");
        assert(table_elem, "Try to find table_elem with ID 'table' -- not found");
        config.table_elem = table_elem;
    }

    if (!config.save_changes_btn_elem) {
        const save_changes_btn_elem = document.getElementById("save-changes");
        assert(save_changes_btn_elem, "Try to find save_changes_btn_elem with id `save-changes` -- not found");
        config.save_changes_btn_elem = save_changes_btn_elem;
    }
    
    const manager = new TableManager({
        submitChangedDataApiUrl: config.submitChangedDataApiUrl
    });
    
    const options = config.getOptions(manager);
    manager.createAndInitializeTable(config.table_elem, config.getDataApiUrl, options);
    config.save_changes_btn_elem.addEventListener('click', manager.saveChangedBtnClickEvent);
    
    return manager;
}
